﻿using System;
using System.Runtime.CompilerServices;
using System.Windows;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace Ecobee.Net
{
	public abstract class ApiObject : IApiObject
	{
		private string _uniqueKey = Guid.NewGuid().ToString();
		private string _jsonText = string.Empty;
		private Api _api = null;

		/// <summary>
		/// Creates a= default instance of Ecobee.Net.Api
		/// </summary>
		public ApiObject()
		{
		}

		/// <summary>
		/// Creates an instance of Ecobee.Net.ApiObject with the specified
		/// Ecobee.Net.Api object.
		/// </summary>
		/// <param name="api"></param>
		public ApiObject(Api api)
		{
			this.Api = api;
		}

		/// <summary>
		/// Gets a reference to the active Ecobee.Net.Api object.
		/// </summary>
		[JsonIgnore]
		protected Api Api
		{
			get
			{
				return _api;
			}
			set
			{
				_api = value;
			}
		}

		/// <summary>
		/// Returns a key that uniquely identifies this instance.
		/// </summary>
		[JsonIgnore]
		public virtual string UniqueKey
		{
			get
			{
				return _uniqueKey;
			}
		}

		/// <summary>
		/// Extracts and returns the unique key from the 
		/// underlying JSON object.
		/// </summary>
		/// <param name="dataObject">The deserialized JSON object.</param>
		/// <returns>A System.String key that uniquely identifies this instance.</returns>
		public virtual string GetKeyValue(dynamic dataObject)
		{
			return this.OnDeserializeKeyValue(dataObject);
		}

		/// <summary>
		/// Convert this object instance to JSON.
		/// </summary>
		/// <returns>A System.String value representing the JSON equivalent of this object.</returns>
		public virtual string Serialize(Formatting formatting = Formatting.None)
		{
			return this.OnSerialize(formatting);
		}

		/// <summary>
		/// Load properties from a JSON string.
		/// </summary>
		/// <param name="json">A System.String value representing the JSON equivalent of this object.</param>
		public virtual void LoadFrom(string json)
		{
			this.OnLoadFrom(json);
		}

		/// <summary>
		/// Load properties from a JSON object.
		/// </summary>
		/// <param name="dataObject">A JSON representation of this object.</param>
		public virtual void LoadFrom(dynamic dataObject)
		{
			if (dataObject != null)
			{
				this.OnLoadFrom(dataObject);
			}
		}

		/// <summary>
		/// Converts this instance into a JSON object.
		/// </summary>
		/// <returns></returns>
		public virtual dynamic JsonObject()
		{
			return JsonConvert.DeserializeObject(this.Serialize());
		}

		[JsonIgnore]
		public string JsonText
		{
			get
			{
				return _jsonText;
			}
		}

		protected virtual string OnSerialize(Formatting formatting = Formatting.None)
		{
			// ***
			// *** Do a default serialization. In most cases if this
			// *** object is used by the API this will not work because
			// *** the property names do not match.
			// ***
			JsonSerializerSettings settings = new JsonSerializerSettings();
			
			settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
			settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;

			return JsonConvert.SerializeObject(this, formatting, settings);
		}

		protected virtual void OnLoadFrom(string json)
		{
			// ***
			// *** Objects cannot be de-serialize directly into their
			// *** types because this result in a new instance of 
			// *** that object. Since all classes implement
			// *** INotifyPropertyChanged, new instances will
			// *** disconnect any class "listening" for changes.
			// ***
			if (!string.IsNullOrEmpty(json))
			{
				_jsonText = json;
				JsonConvert.PopulateObject(json, this);
			}
		}

		protected virtual void OnLoadFrom(object dataObject)
		{
			// ***
			// *** Objects cannot be de-serialize directly into their
			// *** types because this result in a new instance of 
			// *** that object. Since all classes implement
			// *** INotifyPropertyChanged, new instances will
			// *** disconnect any class "listening" for changes.
			// ***
			_jsonText = JsonConvert.SerializeObject(dataObject, Formatting.Indented);
			this.OnLoadFrom(_jsonText);
		}

		protected virtual string OnDeserializeKeyValue(dynamic dataObject)
		{
			return string.Empty;
		}

		public override bool Equals(object obj)
		{
			bool returnValue = false;

			if (obj is ApiObject)
			{
				returnValue = (this.UniqueKey == ((ApiObject)obj).UniqueKey);
			}

			return returnValue;
		}

		public override int GetHashCode()
		{
			return RuntimeHelpers.GetHashCode(this);
		}
	}
}
